package com.navinfo.platform.trip.analysis.util;

import com.navinfo.location.mileage.bean.MileageAndFuel;
import com.navinfo.platform.trip.common.enums.SystemTypeEnum;
import com.navinfo.platform.trip.analysis.service.MilAndFuelService;
import com.navinfo.platform.trip.analysis.service.ServiceFactory;
import com.navinfo.platform.trip.common.arithmetic.Mileage;
import com.navinfo.platform.trip.common.arithmetic.data.CommonData;
import com.navinfo.platform.trip.common.arithmetic.data.DrivingData;
import com.navinfo.platform.trip.common.arithmetic.data.InterCrossDay;
import com.navinfo.platform.trip.common.arithmetic.data.OuterStatisticData;
import com.navinfo.platform.trip.common.arithmetic.var.VarMileageFuel;
import com.navinfo.platform.trip.common.pojo.PointProtocol;
import com.navinfo.platform.trip.common.enums.ProtocolEnum;
import com.navinfo.platform.trip.common.util.ArithmeticUtils;
import com.navinfo.platform.trip.common.util.CommonUtils;
import com.navinfo.platform.trip.common.util.DateUtils;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;

/**
 * 行程统计工具类
 * @author web
 */
public class TripStatisticUtils {
    private static final Logger logger = LoggerFactory.getLogger(TripStatisticUtils.class);

    /**
     * 使用公共轨迹协议赋值算法链的公共数据CommonData
     * @param point 公共轨迹点
     * @param commonData 算法链的公共数据
     */
    public static void assignData(PointProtocol point, CommonData commonData) {
        commonData.setGpsTime(point.getGpsTime());
        commonData.setLongitude(point.getLongitude());
        commonData.setLatitude(point.getLatitude());
        commonData.setOriginalLat(point.getOriginalLat());
        commonData.setOriginalLng(point.getOriginalLng());
        commonData.setHeight((int) point.getHeight());
        commonData.setGpsDirection(point.getDirection());
        commonData.setSpeedKmh((int) point.getSpeedKmh());
        commonData.setSpeedMs(BigDecimal.valueOf(point.getSpeedKmh()).divide(new BigDecimal("3.6"),  2, BigDecimal.ROUND_HALF_UP));
        commonData.setBrake(point.getBrake()==1);
        commonData.setRotation(point.getRotation());
        commonData.setClutchSwitch(point.getClutchSwitch()==1 );
        commonData.setAcceleratorPosition(point.getAccPedalPos() );
        commonData.setEngineOutputTorque(point.getEngineOutputTorque() );
        commonData.setFuelConsumptionRate(BigDecimal.valueOf(point.getFuelConsumptionRate() ));
        commonData.setRealTimeOilConsumption(BigDecimal.valueOf(point.getRealTimeOilConsumption() ));

        //0f37特有属性；
        if(ProtocolEnum.REALTIME.equals(point.getProtocol())){
            commonData.setAtmosphericTem(point.getAtmosphericTem());
        }

        //赋值0200数据
        if(ProtocolEnum.LOCATION.equals(point.getProtocol())){
            assign0200Data(point, commonData);
        }

        //需要上个点属性计算的指标
        if (commonData.getLastGpsTime() > 0) {
            //与上个点的时间差（毫秒）
            long dffTime = point.getGpsTime() - commonData.getLastGpsTime();
            commonData.setDffTime((int) dffTime);

            if (dffTime > 0) {
                //计算加速度
                if(commonData.getLastSpeedMs()!=null && commonData.getSpeedMs()!=null) {
                    try {
                        commonData.setAcceleration(ArithmeticUtils.calcAcceleration(commonData.getLastSpeedMs().doubleValue(), commonData.getSpeedMs().doubleValue(), dffTime));
                    }catch (Exception ex){
                        //如果计算加速度报错，则使用上一次的加速度
                        logger.error("计算加速度失败，使用上一次的加速度",ex);
                    }
                }else{
                    commonData.setAcceleration(0);
                }
            }

        }
    }

    /**
     * 将0200相关指标放到算法链的公共数据CommonData中
     * @param commonData 算法链的公共数据
     * @param pointProtocol 0200的公共轨迹协议
     */
    public static void assign0200Data(PointProtocol pointProtocol, CommonData commonData) {
        commonData.setCurve(pointProtocol.getCurveStatus() == 1);
        commonData.setAcc(pointProtocol.getAccStatus() == 1);
        commonData.setHandBrake(pointProtocol.getHandBrakeStatus()==1);
        commonData.setCruiseControl(pointProtocol.getCruiseControl()==1);
        commonData.setSlideNeutral(pointProtocol.getSlideNeutralStatus()==1);
        commonData.setSharpTurning(pointProtocol.getSharpTurningStatus() == 1);
        commonData.setSlamTheAccelerator(pointProtocol.getSlamAcceleratorStatus() == 1);
        commonData.setRapidAcceleration(pointProtocol.getRapidAccelerationStatus() == 1);
        commonData.setRapidDeceleration(pointProtocol.getRapidDecelerationStatus() == 1);
        commonData.addCoastFlameout(pointProtocol.getCoastFlameoutStatus() == 1);

        commonData.setAmtLoad(pointProtocol.getAmtLoad());
        commonData.setVecuLoad(pointProtocol.getVecuLoad());
        commonData.setTotalOil(pointProtocol.getTotalFuelCons());
        commonData.setWaterTemp(pointProtocol.getCoolingWaterTem());
        commonData.setIntakeAirTem(pointProtocol.getIntakeAirTem());
        commonData.setIntakePressure(pointProtocol.getIntakePressure());
        commonData.setOilPercentage(pointProtocol.getOilPercentage());
    }

    /**
     * 批计算的跨天处理<br/>
     * 当前的业务逻辑：一个行程是只允许跨一天。<br/>
     * 第一次跨天：昨天最后一个Trip的crossDay=0、DrivingData的crossDay=0，<br/>
     *             今天第一个Trip的crossDay=0<br/>
     *             今天最后一个Trip的crossDay=2、DrivingData的crossDay=2<br/>
     *
     * 昨天跨天：昨天最后一个Trip的crossDay=2、DrivingData的crossDay=2，<br/>
     *           今天第一个Trip的crossDay=1，<br/>
     *               如果今天就一个行程，最后一个Trip的crossDay=0、DrivingData的crossDay=0<br/>
     *               如果今天有多个行程，DrivingData的crossDay值和最后一个Trip的crossDay值相同<br/>
     * @param outerStatistics 输出的行程统计信息
     * @param drivingData     中间变量中存储的跨天标志是昨天的跨天标志
     */
    public static void dealBatchCrossDay(List<OuterStatisticData> outerStatistics, DrivingData drivingData) {
        if (CommonUtils.isCollectionEmpty(outerStatistics)) {
            drivingData.setCrossDayFlag(0);
            return;
        }

        OuterStatisticData firstTrip = outerStatistics.get(0);
        OuterStatisticData lastTrip = outerStatistics.get(outerStatistics.size() - 1);

        //昨天最后一个行程的跨天标志
        int preStatus = drivingData.getCrossDayFlag();
        //今天最后一个行程的跨天标志
        int lastStatus = lastTrip.getCrossDayFlag();

        /* 第一次跨天：昨天最后一个Trip的crossDay=0、DrivingData的crossDay=0
         *             今天第一个Trip的crossDay=0<br/>
         *             今天最后一个Trip的crossDay=2、DrivingData的crossDay=2<br/>
         */
        if (preStatus == 0 ) {
            //今天第一个行程为0
            firstTrip.setCrossDayFlag(0);

            if (lastStatus == 2) {
                drivingData.setCrossDayFlag(2);

                //防止第一个行程和最后一个行程是同一个，重新赋值
                lastTrip.setCrossDayFlag(2);
            }
        }

        /*昨天跨天：昨天最后一个Trip的crossDay=2、DrivingData的crossDay=2，<br/>
         *          今天第一个Trip的crossDay=1，<br/>
         *               如果今天就一个行程，最后一个Trip的crossDay=0、DrivingData的crossDay=0<br/>
         *               如果今天有多个行程，DrivingData的crossDay值和最后一个Trip的crossDay值相同<br/>
         */
        else {
            //今天的第一条行程设置跨天标识
            firstTrip.setCrossDayFlag(1);

            //今天就一个行程
            if (firstTrip == lastTrip) {
                //强制最后一个点的跨天标志位0
                lastTrip.setCrossDayFlag(0);
                drivingData.setCrossDayFlag(0);
            }
            //今天有多个行程，DrivingData的crossDay值和最后一个Trip的crossDay值相同
            else{
                drivingData.setCrossDayFlag(lastStatus);
            }
        }
    }

    /**
     * 流计算的跨天处理<br/>
     * 上下两个位置点不在同一天的时候，收集前半段的积分里程、油耗、时长放入drivingData<br/>
     * @param drivingData 算法链的缓存数据
     */
    public static void dealStreamCrossDay(DrivingData drivingData){
        CommonData commonData = drivingData.getCommonData();

        //不是行程第一个点，并且上个点和当天点不是同一天
        if(!commonData.isTripFirstNode() && DateUtils.isSameDay(commonData.getLastGpsTime(), commonData.getGpsTime())){
            InterCrossDay interCrossDay = new InterCrossDay();

            //收集前半段的积分里程、油耗、时长
            VarMileageFuel mileageFuelData = (VarMileageFuel) drivingData.getOrCreateStatisticData(Mileage.class.getSimpleName(), VarMileageFuel.class);
            if (mileageFuelData != null) {
                interCrossDay.setTripLen(ArithmeticUtils.mileageMToM(mileageFuelData.getRouteIntegralMileage()));
                interCrossDay.setTripOil((int) mileageFuelData.getRouteIntegralFuelConsumption());
                interCrossDay.setTripTime(BigDecimal.valueOf(mileageFuelData.getEngineWorkTime()).setScale(0, BigDecimal.ROUND_HALF_UP).intValue());
            }

            drivingData.setCrossDayFlag(1);
            drivingData.setInterCrossDay(interCrossDay);
        }
    }

    /**
     * 根据0200的点，获取精准后的里程油耗
     * @param systemType 系统类型
     * @param points 0200位置点
     * @return 精准后的里程油耗
     */
    public static MileageAndFuel calcMileageFuel(SystemTypeEnum systemType, List<PointProtocol> points){
        try {
            MilAndFuelService mileageAndFuel = ServiceFactory.singleServiceImpl(systemType, MilAndFuelService.class);
            return mileageAndFuel.calcMileageFuel(points);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return new MileageAndFuel();
    }

    /**
     * 计算行程开始和结束时的里程、油耗
     * @param statisticData 统计数据
     * @param points        位置点0200的公共轨迹
     */
    public static void calcStartEndMileageFuel(OuterStatisticData statisticData, List<PointProtocol> points) {
        if (CollectionUtils.isEmpty(points)) {
            return;
        }

        //获取行程开始的位置点
        PointProtocol validPoint = null;
        for (PointProtocol p : points) {
            Float standardFuelCons = p.getStandardFuelCons();
            Float standardMileage = p.getStandardMileage();
            if (ProtocolEnum.LOCATION.equals(p.getProtocol()) && null!=standardFuelCons && null!=standardMileage && standardFuelCons>0 && standardMileage>0) {
                validPoint = p;
                break;
            }
        }

        /* 设置行程开始结束里程油耗
        行程开始里程routeStartMileage：取0200第一个标准里程和标准油耗都>0点的标准里程StandardMileage
        行程开始油耗routeStartFuel：取0200第一个标准里程和标准油耗都>0点的标准油耗StandardFuelCons

        行程结束里程：行程开始里程routeStartMileage+行程内里程tripMileage
        行程结束油耗：行程开始油耗routeStartFuel+行程内里程tripFuel

        流计算
        行程内里程tripMileage：根据0200点精准的行程里程
        行程内里程tripFuel：根据0200点精准的行程油耗
        批计算
        行程内里程tripMileage：（该行程根据0200的精准里程/当天所有行程的精准里程和）* 根据0200点精准的当天总里程
        行程内里程tripFuel：（该行程根据0200的精准油耗/当天所有行程的精准油耗和）* 根据0200点精准的当天总油耗
        */
        if (validPoint != null) {
            int startMileage = ArithmeticUtils.mileageKMToM(validPoint.getStandardMileage());
            int startFuel = ArithmeticUtils.fuelLToMl(validPoint.getStandardFuelCons());
            statisticData.setRouteStartMileage(startMileage);
            statisticData.setRouteStartFuel(startFuel);
            statisticData.setRouteEndMileage(startMileage + statisticData.getTripMileage());
            statisticData.setRouteEndFuel(startFuel + statisticData.getTripFuel());

            //cross_cities_detail加入行程开始里程油耗
            Optional.ofNullable(statisticData.getCrossCitiesDetail()).ifPresent(a ->{
                a.forEach(e -> {
                    e.setMileage(startMileage + e.getMileage() );
                    e.setFuel(startFuel + e.getFuel() );
                });
            });
        } else {
            logger.error("{}未找到有效的0200点，故不计算行程开始结束里程油耗...", statisticData.getTid());
        }
    }
}
